﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BattleOfColors.Control;

namespace BattleOfColors.Model
{
    /// <summary>
    /// Enum responsável por representar a cor do soldado, ou seja, indicar seu time.
    /// </summary>
    public enum Color
    {
        GREEN, RED
    }

    public enum Movement
    {
        UP, LEFT, RIGHT, DOWN, NULL
    }

    /// <summary>
    /// Classe responsável por representar um soldado de um dado time no campo de batalha.
    /// </summary>
    public class Soldier : IComparable
    {
        #region atributos das coordenadas
        private int _x;

        public int X
        {
            get { return _x; }
            set { _x = value; }
        }
        private int _y;

        public int Y
        {
            get { return _y; }
            set { _y = value; }
        }
        #endregion

        #region atributos do soldado
        private static int SoldierCounter = 0;

        private int _identifier;

        public int Identifier
        {
            get { return _identifier; }
            set { _identifier = value; }
        }

        private int _life;

        public int Life
        {
            get { return _life; }
            set { _life = value; }
        }

        private int _power;

        public int Power
        {
            get { return _power; }
            set { _power = value; }
        }

        private int _speed;

        public int Speed
        {
            get { return _speed; }
            set { _speed = value; }
        }
        #endregion

        private Behavior _behavior;

        public Behavior Behavior
        {
            get { return _behavior; }
            set { _behavior = value; }
        }

        #region atributos da memória
        private int _enemyStalk;
        /// <summary>
        /// Variável de memória do soldado para guardar a quantidade de turnos que ele passou perseguindo um cheiro de inimigo
        /// </summary>
        public int EnemyStalk
        {
            get { return _enemyStalk; }
            set { _enemyStalk = value; }
        }

        private int _allySearch;
        /// <summary>
        /// Variável de memória do soldado para guardar a quantidade de turnos que ele passou procurando um cheiro de aliado
        /// </summary>
        public int AllySearch
        {
            get { return _allySearch; }
            set { _allySearch = value; }
        }
        #endregion

        private bool _attacked;
        /// <summary>
        /// Para indicar se o soldado foi atacado naquele turno
        /// </summary>
        public bool Attacked
        {
            get { return _attacked; }
            set { _attacked = value; }
        }

        private Color _color;

        /// <summary>
        /// Indica a cor do soldado, ou seja, a qual time pertence.
        /// </summary>
        public Color Color
        {
            get { return _color; }
            set { _color = value; }
        }

        private Tile _location;

        /// <summary>
        /// Indica o tile que o soldado está localizado
        /// </summary>
        public Tile Location
        {
            get { return _location; }
            set { _location = value; }
        }

        /// <summary>
        /// Verifica se o soldado está vivo.
        /// </summary>
        /// <returns>retorna true caso a vida do soldado seja maior que 0 (zero)</returns>
        public bool isAlive()
        {
            return Life > 0;
        }

        /// <summary>
        /// Construtor default de soldado.
        /// 
        /// Inicializando as variáveis:
        /// Moved = false.
        /// X = -1;
        /// Y = -1;
        /// </summary>
        /// <param name="Life"></param>
        /// <param name="Power"></param>
        /// <param name="color"></param>
        public Soldier(int Identifier, int Life, int Power, int Speed, Color color)
        {
            this.Identifier = Identifier;
            this.Life = Life;
            this.Power = Power;
            this.Speed = Speed;

            this.Color = color;

            this.X = -1;
            this.Y = -1;

            EnemyStalk = 0;
            AllySearch = 0;

            Attacked = false;
        }

        public void Update()
        {
            if (Life > 0)
            {
                Tile upTile = (Tile.AvailableTile(Location.I, Location.J - 1)) ? Specification.Instance.Battlefield[Location.I, Location.J - 1] : null;
                Tile farUpTile = (Tile.AvailableTile(Location.I, Location.J - 2)) ? Specification.Instance.Battlefield[Location.I, Location.J - 2] : null;
                Tile upLeftTile = (Tile.AvailableTile(Location.I - 1, Location.J - 1)) ? Specification.Instance.Battlefield[Location.I - 1, Location.J - 1] : null;
                Tile leftTile = (Tile.AvailableTile(Location.I - 1, Location.J)) ? Specification.Instance.Battlefield[Location.I - 1, Location.J] : null;
                Tile farLeftTile = (Tile.AvailableTile(Location.I - 2, Location.J)) ? Specification.Instance.Battlefield[Location.I - 2, Location.J] : null;
                Tile downLeftTile = (Tile.AvailableTile(Location.I - 1, Location.J + 1)) ? Specification.Instance.Battlefield[Location.I - 1, Location.J + 1] : null;
                Tile downTile = (Tile.AvailableTile(Location.I, Location.J + 1)) ? Specification.Instance.Battlefield[Location.I, Location.J + 1] : null;
                Tile farDownTile = (Tile.AvailableTile(Location.I, Location.J + 2)) ? Specification.Instance.Battlefield[Location.I, Location.J + 2] : null;
                Tile downRightTile = (Tile.AvailableTile(Location.I + 1, Location.J + 1)) ? Specification.Instance.Battlefield[Location.I + 1, Location.J + 1] : null;
                Tile rightTile = (Tile.AvailableTile(Location.I + 1, Location.J)) ? Specification.Instance.Battlefield[Location.I + 1, Location.J] : null;
                Tile farRightTile = (Tile.AvailableTile(Location.I + 2, Location.J)) ? Specification.Instance.Battlefield[Location.I + 2, Location.J] : null;
                Tile upRightTile = (Tile.AvailableTile(Location.I + 1, Location.J - 1)) ? Specification.Instance.Battlefield[Location.I + 1, Location.J - 1] : null;

                float upTileWeight = GetWeight(upTile);
                float farUpTileWeight = GetWeight(farUpTile);
                float upLeftTileWeight = GetWeight(upLeftTile);
                float upRightTileWeight = GetWeight(upRightTile);
                float downTileWeight = GetWeight(downTile);
                float farDownTileWeight = GetWeight(farDownTile);
                float downLeftTileWeight = GetWeight(downLeftTile);
                float downRightTileWeight = GetWeight(downRightTile);
                float leftTileWeight = GetWeight(leftTile);
                float farLeftTileWeight = GetWeight(farLeftTile);
                float rightTileWeight = GetWeight(rightTile);
                float farRightTileWeight = GetWeight(farRightTile);


                float upWeight = upTileWeight + farUpTileWeight + upLeftTileWeight + upRightTileWeight + Behavior.Up;
                float downWeight = downTileWeight + farDownTileWeight + downLeftTileWeight + downRightTileWeight + Behavior.Down;
                float leftWeight = leftTileWeight + farLeftTileWeight + upLeftTileWeight + downLeftTileWeight + Behavior.Left;
                float rightWeight = rightTileWeight + farRightTileWeight + upRightTileWeight + downRightTileWeight + Behavior.Right;

                float standWeight = Behavior.Wait + (1 - Math.Abs((Specification.Instance.DefaultLife / Life))) * Behavior.Group;

                Movement moveTo = MoveTo(upWeight, downWeight, leftWeight, rightWeight, standWeight);

                Tile actualTile = Location;

                actualTile.NewSmell(new Smell(this));

                Tile nextTile = GetTileAt(moveTo);

                if (nextTile != null && nextTile.PositionedSoldier != null)
                {
                    Soldier positionedSoldier = nextTile.PositionedSoldier;

                    if (OpposedColor(positionedSoldier))
                    {
                        Attack(positionedSoldier);
                    }
                    else
                    {
                        Recover();
                    }
                }
                else
                {
                    Move(moveTo);
                }
            }
        }

        private Movement MoveTo(float upWeight, float downWeight, float leftWeight, float rightWeigt, float standWeight)
        {
            Direction upDirection = new Direction(Movement.UP, upWeight);
            Direction downDirection = new Direction(Movement.DOWN, downWeight);
            Direction leftDirection = new Direction(Movement.LEFT, leftWeight);
            Direction rightDirection = new Direction(Movement.RIGHT, rightWeigt);
            Direction standDirection = new Direction(Movement.NULL, standWeight);

            List<Direction> listAllDirections = new List<Direction>();
            listAllDirections.Add(upDirection);
            listAllDirections.Add(downDirection);
            listAllDirections.Add(leftDirection);
            listAllDirections.Add(rightDirection);
            listAllDirections.Add(standDirection);
            listAllDirections.Sort();

            if (listAllDirections[0].Weight != 0)
            {
                List<Direction> listHighestDirections = new List<Direction>();

                float higherWeight = listAllDirections[0].Weight;

                foreach (Direction direction in listAllDirections)
                {
                    if (direction.Weight == higherWeight)
                    {
                        listHighestDirections.Add(direction);
                    }
                }

                Movement moveTo = IntelligentExplore(listHighestDirections);

                return moveTo;
            }
            else
            {
                return Explore();
            }
        }

        /// <summary>
        /// Método para decidir de modo inteligente, baseado em pesos, qual lado seguir a exploração.
        /// Caso exista empate entre tiles, a escolha é feita de modo aleatória.
        /// </summary>
        /// <param name="directions"></param>
        /// <returns></returns>
        private Movement IntelligentExplore(List<Direction> directions)
        {
            if (directions.Count <= 0)
            {
                return Movement.NULL;
            }
            Random random = Specification.Instance.GetRandomColor(Color);

            Movement moveTo = Movement.NULL;
            int counter = 0;
            do
            {
                int value = random.Next(directions.Count);
                moveTo = directions[value].Movement;
                counter++;
            } while (!Tile.AvailableMovement(moveTo, Location.I, Location.J) && counter < directions.Count);

            return moveTo;
        }

        private Movement Explore()
        {
            Tile upTile = (Tile.AvailableTile(Location.I, Location.J - 1)) ? Specification.Instance.Battlefield[Location.I, Location.J - 1] : null;
            Tile leftTile = (Tile.AvailableTile(Location.I - 1, Location.J)) ? Specification.Instance.Battlefield[Location.I - 1, Location.J] : null;
            Tile downTile = (Tile.AvailableTile(Location.I, Location.J + 1)) ? Specification.Instance.Battlefield[Location.I, Location.J + 1] : null;
            Tile rightTile = (Tile.AvailableTile(Location.I + 1, Location.J)) ? Specification.Instance.Battlefield[Location.I + 1, Location.J] : null;

            if (upTile == null)
            {
                Behavior.ReduceMovementTendencie(Movement.UP, 9);
            }
            else
            {
                if (upTile.Smell != null && upTile.Smell.SoldierIdentifier == Identifier)
                {
                    Behavior.ReduceMovementTendencie(Movement.UP, 3);
                }
            }
            if (leftTile == null)
            {
                Behavior.ReduceMovementTendencie(Movement.LEFT, 9);
            }
            else
            {
                if (leftTile.Smell != null && leftTile.Smell.SoldierIdentifier == Identifier)
                {
                    Behavior.ReduceMovementTendencie(Movement.LEFT, 3);
                }
            }
            if (downTile == null)
            {
                Behavior.ReduceMovementTendencie(Movement.DOWN, 9);
            }
            else
            {
                if (downTile.Smell != null && downTile.Smell.SoldierIdentifier == Identifier)
                {
                    Behavior.ReduceMovementTendencie(Movement.DOWN, 3);
                }
            }
            if (rightTile == null)
            {
                Behavior.ReduceMovementTendencie(Movement.RIGHT, 9);
            }
            else
            {
                if (rightTile.Smell != null && rightTile.Smell.SoldierIdentifier == Identifier)
                {
                    Behavior.ReduceMovementTendencie(Movement.RIGHT, 3);
                }
            }

            Random random = Specification.Instance.GetRandomColor(Color);

            //System.Diagnostics.Debug.WriteLine("Total explore! - " + Behavior.Left + " " + Behavior.Up + " " + Behavior.Left + " " + Behavior.Down);

            int value = random.Next(100);

            if (value < Behavior.Left)
            {
                return Movement.LEFT;
            }
            else if (value < Behavior.Left + Behavior.Up)
            {
                return Movement.UP;
            }
            else if (value < Behavior.Left + Behavior.Right + Behavior.Up)
            {
                return Movement.RIGHT;
            }
            else
            {
                return Movement.DOWN;
            }
        }

        /// <summary>
        /// Cálcula o peso de determinado tile baseando-se nos cheiros e soldados presentes
        /// </summary>
        /// <param name="tile"></param>
        /// <returns></returns>
        private float GetWeight(Tile tile)
        {
            if (tile != null)
            {
                if (tile.PositionedSoldier != null)
                {
                    Soldier tileSoldier = tile.PositionedSoldier;

                    if (OpposedColor(tileSoldier))
                    {
                        if (tileSoldier.Life <= 0)
                        {
                            return 0f;
                        }
                        else
                        {
                            return ((Life / tileSoldier.Life) * Behavior.Attack - Behavior.Run) * 4;
                        }
                    }
                    else
                    {
                        if (tileSoldier.Life <= 0)
                        {
                            return 0f;
                        }
                        else
                        {
                            float fractionAlly = Math.Abs(1 - (Specification.Instance.DefaultLife / tileSoldier.Life));
                            float fractionSelf = Math.Abs(1 - (Specification.Instance.DefaultLife / Life));
                            return ((fractionAlly * (Behavior.Help / 2) + fractionSelf) * Behavior.Group - Behavior.Attack);
                        }
                    }
                }
                else
                {
                    if (tile.HaveAnySmell())
                    {
                        Smell smell = tile.Smell;

                        SmellType smellType = smell.Type;

                        float multiplier = 1;

                        if (smell.IsBloodSmell())
                        {
                            multiplier = 1.2f;
                        }

                        if (OpposedSmell(smellType))
                        {
                            float motivation = 1 / Math.Abs(Behavior.GiveUp - EnemyStalk);
                            float fractionSelf = (Life / Specification.Instance.DefaultLife);
                            float goOn = ((Behavior.GiveUp - EnemyStalk) > 0) ? 1 : motivation;
                            return goOn * ((Behavior.Attack*(smell.RemaingTime / 10)*multiplier)*fractionSelf);
                            //old formule goOn * (fractionSelf * Behavior.Attack + (smell.RemaingTime / 10) * multiplier);
                        }
                        else
                        {
                            if (smell.SoldierIdentifier == Identifier)
                            {
                                return 0f;
                            }
                            else
                            {
                                if (Life <= 0)
                                {
                                    return 0f;
                                }
                                else
                                {
                                    float motivation = 1 / Math.Abs(Behavior.GiveUp - AllySearch);
                                    float fractionSelf = -1*(Life / Specification.Instance.DefaultLife) + 1.1f;
                                    float goOn = ((Behavior.GiveUp - AllySearch) > 0) ? 1 : motivation;
                                    return goOn * ((Behavior.Group * (smell.RemaingTime / 10) * multiplier) * fractionSelf);
                                    //old formule goOn * (fractionSelf * Behavior.Group + (smell.RemaingTime / 10) * multiplier);
                                }
                            }
                        }
                    }
                    else
                    {
                        return Behavior.Explore;
                    }
                }
            }
            return 0f;
        }

        /// <summary>
        /// Método para dado uma direção, retornar o tile daquela direção
        /// </summary>
        /// <param name="move"></param>
        /// <returns></returns>
        private Tile GetTileAt(Movement move)
        {
            switch (move)
            {
                case Movement.LEFT:
                    if (Tile.AvailableTile(Location.I - 1, Location.J))
                    {
                        return Specification.Instance.Battlefield[Location.I - 1, Location.J];
                    }
                    break;
                case Movement.RIGHT:
                    if (Tile.AvailableTile(Location.I + 1, Location.J))
                    {
                        return Specification.Instance.Battlefield[Location.I + 1, Location.J];
                    }
                    break;
                case Movement.UP:
                    if (Tile.AvailableTile(Location.I, Location.J - 1))
                    {
                        return Specification.Instance.Battlefield[Location.I, Location.J - 1];
                    }
                    break;
                case Movement.DOWN:
                    if (Tile.AvailableTile(Location.I, Location.J + 1))
                    {
                        return Specification.Instance.Battlefield[Location.I, Location.J + 1];
                    }
                    break;
            }

            return null;
        }

        /// <summary>
        /// Método para movimentar o soldado em uma das posições possíveis, determinadas pelo enum Movement
        /// </summary>
        /// <param name="move"></param>
        private void Move(Movement move)
        {
            Tile newLocation = null;

            switch (move)
            {
                case Movement.LEFT:
                    if (Tile.AvailableTile(Location.I - 1, Location.J) && Specification.Instance.Battlefield[Location.I - 1, Location.J].PositionedSoldier == null)
                    {
                        newLocation = Specification.Instance.Battlefield[Location.I - 1, Location.J];
                    }
                    break;
                case Movement.UP:
                    if (Tile.AvailableTile(Location.I, Location.J - 1) && Specification.Instance.Battlefield[Location.I, Location.J - 1].PositionedSoldier == null)
                    {
                        newLocation = Specification.Instance.Battlefield[Location.I, Location.J - 1];
                    }
                    break;
                case Movement.DOWN:
                    if (Tile.AvailableTile(Location.I, Location.J + 1) && Specification.Instance.Battlefield[Location.I, Location.J + 1].PositionedSoldier == null)
                    {
                        newLocation = Specification.Instance.Battlefield[Location.I, Location.J + 1];
                    }
                    break;
                case Movement.RIGHT:
                    if (Tile.AvailableTile(Location.I + 1, Location.J) && Specification.Instance.Battlefield[Location.I + 1, Location.J].PositionedSoldier == null)
                    {
                        newLocation = Specification.Instance.Battlefield[Location.I + 1, Location.J];
                    }
                    break;
            }

            if (newLocation != null)
            {
                Location.PositionedSoldier = null;
                Location = newLocation;
                newLocation.PositionedSoldier = this;

                if (newLocation.Smell != null)
                {
                    if (OpposedSmell(newLocation.Smell))
                    {
                        EnemyStalk++;
                        AllySearch = 0;
                    }
                    else
                    {
                        EnemyStalk = 0;
                        AllySearch++;
                    }
                }
                else
                {
                    AllySearch = 0;
                    EnemyStalk = 0;
                }

                X = newLocation.X;
                Y = newLocation.Y;
            }
        }

        /// <summary>
        /// Método para realizar um ataque ao alvo passado.
        /// O ataque é uma simples redução na vida do alvo na quantidade do poder do soldado.
        /// Contém verificação se o alvo é válido.
        /// </summary>
        /// <param name="target"></param>
        public void Attack(Soldier target)
        {
            //Para garantir que o soldado ainda está vivo
            if (Life > 0)
            {
                //Para garantir que é um inimigo válido
                if (target != null && OpposedColor(target))
                {
                    target.Life -= this.Power;
                    target.Attacked = true;
                }
            }
        }

        /// <summary>
        /// Método para recuperar a vida de um soldado caso esteja próximo de outro de sua equipe.
        /// </summary>
        public void Recover()
        {
            if (Life < Specification.Instance.DefaultLife)
            {
                Life++;
            }
        }

        /// <summary>
        /// Método para verificar se o soldado passado como parâmetro possui cor diferente a este
        /// </summary>
        /// <param name="soldier"></param>
        /// <returns></returns>
        public bool OpposedColor(Soldier soldier)
        {
            return soldier.Color != this.Color;
        }

        /// <summary>
        /// Método para retornar se o cheiro é da mesma cor que o soldado
        /// </summary>
        /// <param name="smell"></param>
        /// <returns></returns>
        public bool OpposedSmell(Smell smell)
        {
            return OpposedSmell(smell.Type);
        }

        /// <summary>
        /// Método para retornar se o cheiro é da mesma cor que o soldado
        /// </summary>
        /// <param name="smellType"></param>
        /// <returns></returns>
        public bool OpposedSmell(SmellType smellType)
        {
            switch (smellType)
            {
                case SmellType.GREEN_SOLDIER:
                    goto case SmellType.GREEN_PAIN;
                case SmellType.GREEN_PAIN:
                    return (Color != Model.Color.GREEN);
                case SmellType.RED_SOLDIER:
                    goto case SmellType.RED_PAIN;
                case SmellType.RED_PAIN:
                    return (Color != Model.Color.RED);
                case SmellType.ITEM:
                    break;
                default:
                    return false;
            }

            return false;
        }

        /// <summary>
        /// Método para realizar cópia profunda de um soldado
        /// </summary>
        /// <returns></returns>
        public Soldier cloneSoldier()
        {
            Soldier clone = new Soldier(Identifier, Life, Power, Speed, Color);

            clone.X = X;
            clone.Y = Y;

            return clone;
        }

        /// <summary>
        /// Método para a comparação pela velocidade de dois soldados
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }

            if (obj is Soldier)
            {
                Soldier anotherSoldier = (Soldier)obj;

                if (anotherSoldier.Speed > Speed)
                {
                    return -1;
                }
                else
                {
                    return 1;
                }
            }
            else
            {
                return 1;
            }
        }

        /// <summary>
        /// Gerador de um soldado usando valores default para os pontos de vida e força.
        /// 
        /// Usado os valores especificados na classe Specification.
        /// </summary>
        /// <param name="color"></param>
        /// <returns></returns>
        public static Soldier CreateSoldier(Color color)
        {
            Soldier soldier = new Soldier(++SoldierCounter, Specification.Instance.DefaultLife, Specification.Instance.DefaultPower, Specification.Instance.DefaultSpeed, color);
            soldier.Behavior = Behavior.AggressiveBehavior();
            return soldier;
        }
    }
}